package Partie2;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;

import java.io.InputStreamReader;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import Partie1.BernoulliParser;

import commun.Review;
import commun.ReviewType;
/**
 * @author Yangkun, Chengbo
 * classificateur de la mod鑜e multinomiale semi supervis閑
 */
public class MultinomialAnalyserSemiSupervise {
	/**
	 * statistiques enregistre tous les donnees d'apprentissage 
	 */
	MultinomialParserSemiSupervise statistiques;
	/**
	 * pour chaque review, cette map enregistre son id et son type pronosique.
	 */
	Map<Integer, ReviewType> reviewTypes;
	
	/**
	 * pour enregistrer tous les review du document sans 閠iquette
	 */
	Map<Integer, Review> unLabReviews;
	int nbCorrect = 0;
	int nbFault = 0;
	int nbPosiAsNeg =0;
	int nbNegAsPos =0;
	int nbReview = 0;
	/**
	 * le taux correct.
	 */
	double ratio;
	
	public static void main(String[] args) throws Exception{
		if(args.length < 2)
		{
			System.out.println("Erreur, il faut avoire au au moins deux paramètre.");
			return;
		}
		MultinomialAnalyserSemiSupervise da = new MultinomialAnalyserSemiSupervise(args[0],"mdsa/kitchen/unlab.txt",0.15);
		da.parseReviews(args[1]);
		System.out.println("nombre review: "+da.nbReview+" nb correct: "+ da.nbCorrect +" nb fault: "+ da.nbFault+" ratio: "+da.ratio);
		System.out.println("Positive review as negative: "+ da.nbPosiAsNeg +" Negative review as positive: "+ da.nbNegAsPos);
	}
	
	
	/**
	 * constructeur, initialiser les hashmaps, obtenir les statistiques
	 * @param filetrainging o�se trouve le document d'apprentissage;
	 * @param fileSansEtiquette o�se trouve le document sans etiquette: unlab.txt;
	 * @param lissage : parametre de reglage
	 * @throws Exception
	 */
	public MultinomialAnalyserSemiSupervise(String filetrainging, String fileSansEtiquette, double lissage) throws Exception{
		this.reviewTypes = new HashMap<Integer, ReviewType>();
		this.statistiques = new MultinomialParserSemiSupervise(lissage);
		this.unLabReviews = new HashMap<Integer, Review>();
		statistiques.parseFile(filetrainging);
		parseUnlabReviews(fileSansEtiquette);
		
	}
	
	
	/**
	 * pour le review sans 閠iquette 閠udi� calcule sa posibilit�d'阾re positive et sa posibilit�d'阾re negative;
	 * Et l'enregistrer dans le hashmap unlabReviews.
	 * @param review le review etudi�
	 * @throws Exception
	 */
	public void PreDecideReview(Review review) throws Exception{
		double logPropaPositive =0;
		double logPropaNegative=0;
	    Iterator iter = review.wordsMap.entrySet().iterator(); 
	    while (iter.hasNext()) { 
	    	Map.Entry entry = (Map.Entry) iter.next();
	        String key = (String)entry.getKey();
	        if(this.statistiques.PositiveTable.containsKey(key)){
	        	logPropaPositive += (Double)entry.getValue()*Math.log(this.statistiques.PositiveTable.get(key));
	        }
	        if(this.statistiques.NegativeTable.containsKey(key)){
	        	logPropaNegative += (Double)entry.getValue()*Math.log(this.statistiques.NegativeTable.get(key));
	        }
	    } 
		/**
		 * posons log(b1) = l1, log(b2) = l2, p1+p2=1;
		 * log(b1/b2) = log(b1)-log(b2) = l1-l2;
		 * b1 = b2*exp(l1-l2);
		 * b2 = 1.0/(1.0+exp(l1-l2));
		 * pour eviter que l1-l2 soit trop petit, on va toujours calculer le cas o�l1>l2;
		 */
	    if (logPropaNegative>logPropaPositive) {
	    	 review.propaPositive =1.0/(1.0+Math.exp(logPropaNegative-logPropaPositive));
	    	 review.propaNegative = 1-review.propaPositive;
		}else {
			review.propaNegative =1.0/(1.0+Math.exp(logPropaPositive-logPropaNegative));
			review.propaPositive = 1- review.propaNegative;
		}
	    unLabReviews.put(review.id, review);
	}
	
	
	/**
	 * vider les statistiques de trainning.txt, et r閠ablir la staitistique de unlab.txt
	 */
	public void updateStatistiques(){
		this.statistiques.init();
		Iterator iter = this.unLabReviews.entrySet().iterator(); 
		while (iter.hasNext()) { 
	    	Map.Entry entry = (Map.Entry) iter.next();
	        int key = (Integer)entry.getKey();
	        Review review = (Review)entry.getValue();
	        this.statistiques.nbWordsNegatif += review.nbWords*review.propaNegative;
	        this.statistiques.nbWordsPositif += review.nbWords*review.propaPositive;
		    Iterator wordsIter = review.wordsMap.entrySet().iterator(); 
		    while (wordsIter.hasNext()) { 
		    	Map.Entry entryword = (Map.Entry) wordsIter.next();
		        String word = (String)entryword.getKey();
		        double nbwords = (Double)entryword.getValue();
		        if (this.statistiques.Positifwords.containsKey(word)) {
					double nb = this.statistiques.Positifwords.get(word);
					nb += nbwords * review.propaPositive;
					this.statistiques.Positifwords.put(word, nb);
				}else {
					double nb = nbwords * review.propaPositive;
					this.statistiques.Positifwords.put(word, nb);
				}
		        
		        if (this.statistiques.Negatifwords.containsKey(word)) {
					double nb = this.statistiques.Negatifwords.get(word);
					nb += nbwords * review.propaNegative;
					this.statistiques.Negatifwords.put(word, nb);
				}else {
					double nb = nbwords * review.propaNegative;
					this.statistiques.Negatifwords.put(word, nb);
				}
		        
		    }
		}
		this.statistiques.calculProbabilite();
	}
	
	
	/**
	 * @param review review �閠udier
	 * @return le type pronositique
	 * @throws Exception
	 */
	public ReviewType decideReview(Review review) throws Exception{
		double logPropaPositive =0;
		double logPropaNegative=0;
		ReviewType typePronostic;
	    Iterator iter = review.wordsMap.entrySet().iterator(); 
	    while (iter.hasNext()) { 
	    	Map.Entry entry = (Map.Entry) iter.next();
	        String key = (String)entry.getKey();
	        if(this.statistiques.PositiveTable.containsKey(key)){
	        	logPropaPositive += (Double)entry.getValue()*Math.log(this.statistiques.PositiveTable.get(key));
	        }
	        if(this.statistiques.NegativeTable.containsKey(key)){
	        	logPropaNegative += (Double)entry.getValue()*Math.log(this.statistiques.NegativeTable.get(key));
	        }
	   
	    } 
		if(logPropaPositive > logPropaNegative){
			typePronostic = ReviewType.Positive;
		}else{
			typePronostic = ReviewType.Negative;
		}

		return typePronostic;

	}
	
	/**
	 * parser tous les review dans le fichier.
	 * @param filepath o�se trouve le fichier 閠udi�
	 * @throws Exception
	 */
	public  void parseReviews(String filepath) throws Exception{
		File f = new File(filepath); 
		FileInputStream fstream = new FileInputStream(f);
		DataInputStream in = new DataInputStream(fstream);		
		BufferedReader br = new BufferedReader(new InputStreamReader(in));
		String strLine;
		while ((strLine = br.readLine()) != null)   {
			if(strLine.contains("<review") && strLine.contains("id"))
			{
	    		Review unReview = BernoulliParser.parseUnReview(br, strLine); 
	    		this.nbReview++;
	    		ReviewType typePronostic = decideReview(unReview);
	    		this.reviewTypes.put(unReview.id, typePronostic);
	    		if (unReview.realType!=commun.ReviewType.Unknown) {
	    			if(typePronostic.compareTo(unReview.realType)==0){
		    			this.nbCorrect++;
		    		}else{
		    			if(typePronostic.equals(ReviewType.Positive))
		    				this.nbNegAsPos++;
		    			else
		    				this.nbPosiAsNeg++;
		    			this.nbFault++;
		    		}
				}
			}
		 }
		
		this.ratio = (double)this.nbCorrect/this.nbReview;
	    br.close();
	    in.close();
	    fstream.close(); 
	}
	
	
	/**
	 * parser tous les reviews dans le fichier unlab.txt.
	 * @param filepath o�se trouve le fichier 閠udi�
	 * @throws Exception
	 */
	public  void parseUnlabReviews(String ublabfilepath) throws Exception{
		File f = new File(ublabfilepath); 
		FileInputStream fstream = new FileInputStream(f);
		DataInputStream in = new DataInputStream(fstream);		
		BufferedReader br = new BufferedReader(new InputStreamReader(in));
		String strLine;
		while ((strLine = br.readLine()) != null)   {
			if(strLine.contains("<review") && strLine.contains("id"))
			{
	    		Review unReview = BernoulliParser.parseUnReview(br, strLine); 
	    		PreDecideReview(unReview);
			}
		 }
		updateStatistiques();
	    br.close();
	    in.close();
	    fstream.close(); 
	}

}
